package ClasesBasicas;

import java.awt.Color;
import java.awt.Font;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Vector;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import Ventanas.VentanaTablero;

import BD.GestorBD;
import Enums.ColorFicha;
import Enums.TipoFicha;
import Exceptions.GameException;
import GestionPartida.Casilla;
import GestionPartida.Computadora;
import GestionPartida.Ficha;
import GestionPartida.Tablero;
import GestionUsuarios.GestorSistema;
import GestionUsuarios.Usuario;

/**
 * @author Equipo 04
 * 
 *         Clase partida, utilizada para gestionar una partida de reversi.
 *         Existe una clase Partida por cada partida jugada, no es un gestor
 *         global.
 */
public class Partida {
	private Usuario[] usuarios;
	private Tablero tablero;
	private Date fecha;
	private HashMap<Usuario, Integer> puntuaciones;
	private HashMap<Usuario, ColorFicha> colores;
	private int turnoActual;
	private TipoFicha tipo;
	private VentanaTablero ventana;
	private boolean mContrarreloj;
	private HashMap<Usuario, Contador> contadores;
	private JFrame vAnt;

	/**
	 * Constructor de la partida
	 * 
	 * @param users
	 *            Usuarios que juegan la partida, puede contener bots
	 * @param colors
	 *            colores de cada jugador
	 * @param t
	 *            tablero sobre el que se jugara
	 * @param f
	 *            fecha en la que se juega
	 * @param punts
	 *            puntuacion inicial de cada jugador
	 * @param type
	 *            tipo de ficha en la partida
	 * @param m
	 *            true si es contrarreloj
	 * @throws GameException
	 *             si el numero de puntuaciones, colores o jugadores es ilogico
	 */
	public Partida(Usuario[] users, ColorFicha[] colors, Tablero t, Date f,
			int[] punts, TipoFicha type, boolean m) throws GameException {
		mContrarreloj = m;
		turnoActual = 0;
		tipo = type;
		// Modificar

		// Crear array de usuarios
		if (users.length <= 4 && users.length > 0) {
			usuarios = users;
			for (Usuario u : usuarios) {
				if (u instanceof Computadora) {
					((Computadora) u).setPartida(this);
				}
			}
		} else {
			throw new GameException("Numero de usuario incorrecto");
		}

		// Crear el Hashmap de puntuaciones.
		puntuaciones = new HashMap<Usuario, Integer>();
		if (punts.length != users.length) {
			throw new GameException(
					"Las puntuaciones no coinciden con los jugadores.");
		} else {
			for (int i = 0; i < usuarios.length; i++) {
				puntuaciones.put(users[i], punts[i]);
			}
		}

		// Crear HashMap de colores.
		colores = new HashMap<Usuario, ColorFicha>();
		if (colors.length != users.length) {
			throw new GameException(
					"Los colores no coinciden con los jugadores.");
		} else {
			for (int i = 0; i < usuarios.length; i++) {
				colores.put(users[i], colors[i]);
			}
		}

		// Crear fecha
		if (t != null) {
			tablero = t;
		} else {
			throw new GameException("No hay tablero.");
		}

		// Crear tablero
		if (f != null) {
			fecha = f;
		} else {
			throw new GameException("No hay fecha.");
		}

		if (mContrarreloj) {
			contadores = new HashMap<Usuario, Contador>();
			for (Usuario u : usuarios) {
				contadores.put(u,
						Contador.crearContador(30, colores.get(u), this));
			}
		} else {
			contadores = null;
		}
		ventana = null;
		actualizarPuntuaciones();
	}

	public Usuario[] getUsuarios() {
		return usuarios;
	}

	public Usuario getUsuarioActual() {
		return usuarios[turnoActual];
	}

	public HashMap<Usuario, Integer> getPuntuaciones() {
		return puntuaciones;
	}

	public int getPuntuacionUsuarioActual() {
		return puntuaciones.get(usuarios[turnoActual]).intValue();
	}

	public int getPuntuacion(Usuario u) {
		return puntuaciones.get(u).intValue();
	}

	public void setPuntuacion(Usuario u, int i) {
		puntuaciones.remove(u);
		puntuaciones.put(u, new Integer(i));
	}

	public HashMap<Usuario, ColorFicha> getColores() {
		return colores;
	}

	public ColorFicha getColorUsuarioActual() {
		return colores.get(usuarios[turnoActual]);
	}

	public ColorFicha getColor(Usuario u) {
		return colores.get(u);
	}

	public TipoFicha getTipoFicha() {
		return tipo;
	}

	/**
	 * Contruye la ventana sobre la que se visualizara la partida si no existe y
	 * la devuelve
	 * 
	 * @return la ventan de la partida
	 */
	public VentanaTablero getVentana() {
		if (ventana == null) {
			ventana = new VentanaTablero(this);
		}
		return ventana;
	}

	public void setVentana(VentanaTablero v) {
		ventana = v;
	}

	public Tablero getTablero() {
		return tablero;
	}

	public int getTurnoActual() {
		return turnoActual;
	}

	public boolean ismContrarreloj() {
		return mContrarreloj;
	}

	public void setmContrarreloj(boolean mContrarreloj) {
		this.mContrarreloj = mContrarreloj;
	}

	public Contador getContador(Usuario u) {
		return contadores.get(u);
	}

	public Contador getContadorActual() {
		return contadores.get(usuarios[turnoActual]);
	}

	/**
	 * Pasa al siguiente turno de la partida y comprueba si la partida ha
	 * acabado. si ha acabado cambia el panel de la ventana.
	 */
	public void siguienteTurno() {
		if (!(getUsuarioActual() instanceof Computadora)) {
			ventana.apagarJugador();
		}
		turnoActual++;
		if (turnoActual == usuarios.length) {
			turnoActual = 0;
		}
		if (siguePartida()) {
			if (getUsuarioActual() instanceof Computadora) {
				ejecutarComputadora();
			} else {
				if (mContrarreloj) {
					getContadorActual().ejecutar();
				}
				ventana.resaltarJugador();
			}
		} else {
			ventana.abrirPanelFinal();
		}
	}

	public Date getFecha() {
		return fecha;
	}

	public void setFecha(Date fecha) {
		this.fecha = fecha;
	}

	/**
	 * Actualiza las puntuaciones de cada usuario segun el numero de fichas en
	 * la partida
	 */
	public void actualizarPuntuaciones() {
		for (Usuario u : usuarios) {
			setPuntuacion(u, tablero.getNumeroFichas(getColor(u)));
		}
	}

	/**
	 * Coloca una ficha en el tablero, la correspondiente al usuario actual, y
	 * captura todas las fichas que se ganen con esa jugada. Tambien se encarga
	 * de pasar el turno, visualizar la nueva ficha y actualizar las
	 * puntuaciones.
	 * 
	 * @param c
	 *            Casilla en la que se coloca la ficha.
	 */
	public void colocarFicha(Casilla c) {
		if (c.getFicha() == null) {
			Vector<Casilla> casillasGanadas = tablero.casillasGanadas(c,
					getColorUsuarioActual());
			for (Casilla cas : casillasGanadas) {
				cas.setFicha(new Ficha(tipo, getColorUsuarioActual()));
			}
			if (mContrarreloj) {
				getContadorActual().interrumpir();
			}
			c.setFicha(new Ficha(tipo, getColorUsuarioActual()));
			c.getComponenteVisual().setAlpha(1.0f);
			c.getComponenteVisual().setIcono(
					GestorSistema.getInstance().crearImagen(
							getColorUsuarioActual(), tipo));
			actualizarPuntuaciones();
			ventana.actualizarPuntuaciones();
			siguienteTurno();
		}
	}

	/**
	 * Espera aproximadamente un segundo a que acabe la animacion del anterior
	 * jugador y ejecuta la computadora. Se encarga de colocar la ficha, logica
	 * y visualmente.
	 */
	private void ejecutarComputadora() {
		Thread t = new Thread() {
			public void run() {
				Casilla c = ((Computadora) getUsuarioActual())
						.calcularJugada(usuarios.length / 2);
				try {
					while (GestorSistema.getInstance().isAnimacionFuncionando()) {
						Thread.sleep(60);

					}
					Thread.sleep(800);
					/*
					 * Espera un sgundo, para dar tiempo al usuario a ver las
					 * jugadas y para que no se solapen las diferentes
					 * animaciones
					 */
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				ventana.colocarFichaVisual(c);
				colocarFicha(c);
			}
		};
		t.start();
	}

	/**
	 * Comprueba si continua la partida, en caso de continuar se mueve hasta el
	 * turno del primer jugador que pueda colocar fichas despues del actual.
	 * 
	 * @return True si la partida continua
	 */

	private boolean siguePartida() {

		boolean encontrado = false;
		if (tablero.actualizarCandidatos()) {
			for (int i = 0; i < usuarios.length && !encontrado; i++) {
				if (tablero.sePuedeColocar(getColorUsuarioActual())) {
					encontrado = true;
				} else {
					turnoActual++;
					if (turnoActual == usuarios.length) {
						turnoActual = 0;
					}
				}
			}
		}
		return encontrado;
	}

	/**
	 * Cambia la pantalla del tablero por una pantalla con el fin de la partida.
	 */

	public void acabarPartida() {
		boolean empate = false;
		Usuario ganador = usuarios[0];
		for (Usuario u : usuarios) {
			if (getPuntuacion(u) > getPuntuacion(ganador)) {
				empate = false;
				ganador = u;
			} else {
				if (getPuntuacion(u) == getPuntuacion(ganador) && u != ganador) {
					empate = true;
				}
			}
		}
		cerrar((empate) ? null : ganador);
	}

	/**
	 * Guarda la partida actual en la base de datos, para poder reanudarla en un
	 * futuro.
	 * 
	 * @throws SQLException
	 *             Si hay errores con la BD
	 */

	public void guardar() throws SQLException {

		GestorBD gestor = GestorBD.getInstance();
		gestor.conectar();
		gestor.borrarPartida(tablero.getCodigo());
		for (Usuario u : usuarios) {
			gestor.insertarPartida(u.getNick(), tablero.getCodigo(),
					getColor(u), getPuntuacion(u),
					new java.sql.Date(System.currentTimeMillis()),
					mContrarreloj);
		}
		gestor.insertarTablero(tablero.getCodigo(), getTipoFicha().name(),
				tablero.size(), usuarios.length, turnoActual);
		for (int i = 0; i < tablero.getCasillas().length; i++) {
			for (Casilla c : tablero.getCasillas()[i]) {
				gestor.insertarCasilla(tablero.getCodigo(), c.getPosX(), c
						.getPosY(), (c.getFicha() != null) ? c.getFicha()
						.getColor() : null);
			}
		}
		gestor.desconectar();
	}

	/**
	 * Cierra la ventana de reproducto y la ventana actual. Tambien guarda las
	 * estadisticas de los usuarios en la base de datos.
	 * 
	 * @param u
	 *            Usuario ganador de la partida, si hay empate es nulo.
	 */
	public void cerrar(Usuario u) {
		GestorBD gestor = GestorBD.getInstance();
		gestor.conectar();
		try {
			gestor.borrarTablero(tablero.getCodigo());
			gestor.borrarPartida(tablero.getCodigo());
			for (int i = 0; i < tablero.getCasillas().length; i++) {
				for (Casilla c : tablero.getCasillas()[i]) {
					gestor.borrarCasilla(tablero.getCodigo(), c.getPosX(),
							c.getPosY());
				}
			}
			if (u != null) {
				gestor.setPartidasGanadas(u.getNick(),
						gestor.getPartidasGanadas(u.getNick()) + 1);
			}
			for (Usuario us : usuarios) {
				gestor.setPuntuacion(us.getNick(),
						gestor.getPuntuacion(us.getNick()) + getPuntuacion(us));
				gestor.setPartidasJugadas(us.getNick(),
						gestor.getPartidasJugadas(us.getNick()) + 1);
			}
		} catch (SQLException e) {
			e.printStackTrace();
			JOptionPane.showMessageDialog(null,
					"Error guardando las estadisticas");
		} finally {
			ventana.getReproductor().cerrar();
			ventana.dispose();
			GestorSistema.getInstance().limpiar();
			gestor.desconectar();
		}
	}

	/**
	 * Guarda en la base de datos las estadisticas para esta partida que ya ha
	 * acabado y crea una nueva ventana para jugar la partida de nuevo.
	 */
	public void reiniciar() {
		ColorFicha[] colors = new ColorFicha[colores.size()];
		for (int i = 0; i < usuarios.length; i++) {
			colors[i] = colores.get(usuarios[i]);
		}
		tablero = new Tablero(tablero.getCodigo(), tablero.size(), colors, tipo);
		tablero.actualizarCandidatos();
		actualizarPuntuaciones();
		GestorBD.getInstance().conectar();
		ventana = new VentanaTablero(this);
		GestorBD.getInstance().desconectar();
		ventana.setVisible(true);
	}

	public void setTurno(int turno) {
		turnoActual = turno;
	}

	public void setvAnt(JFrame f) {
		vAnt = f;
	}

	public void volverAMenu() {
		vAnt.setVisible(true);
	}
}
